{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import division  \n",
    "import numpy as np  \n",
    "import scipy as sp  \n",
    "from numpy.random import random  \n",
    "\n",
    "class  SVD_CF:  \n",
    "    def __init__(self, X, k=20):  \n",
    "        ''''' \n",
    "            k  is the number of latent componets \n",
    "        '''  \n",
    "        self.X = X  \n",
    "        self.k = k  \n",
    "        self.mu = np.mean(self.X[:,2])  #average rating\n",
    "        \n",
    "        #init parameters\n",
    "        self.bi={}  \n",
    "        self.bu={}  \n",
    "        \n",
    "        self.qi={}  \n",
    "        self.pu={}  \n",
    "        \n",
    "        self.ItemsForUser={}  #每个Item对应的用户\n",
    "        self.UsersForItem={}  #每个用户对哪些Item打过分\n",
    "        \n",
    "        for i in range(self.X.shape[0]):  \n",
    "            uid=self.X[i][0]  #user id\n",
    "            i_id=self.X[i][1] #item_id \n",
    "            rat=self.X[i][2]  #rating\n",
    "            \n",
    "            self.ItemsForUser.setdefault(i_id,{})  \n",
    "            self.UsersForItem.setdefault(uid,{}) \n",
    "            \n",
    "            self.ItemsForUser[i_id][uid]=rat  \n",
    "            self.UsersForItem[uid][i_id]=rat  \n",
    "            \n",
    "            self.bi.setdefault(i_id,0)  \n",
    "            self.bu.setdefault(uid,0)  \n",
    "            \n",
    "            self.qi.setdefault(i_id,random((self.k,1))/10*(np.sqrt(self.k)))  \n",
    "            self.pu.setdefault(uid,random((self.k,1))/10*(np.sqrt(self.k)))  \n",
    "                    \n",
    "    #根据当前参数，预测用户uid对Item（i_id）的打分\n",
    "    def pred(self,uid,i_id):  \n",
    "        self.bi.setdefault(i_id,0)  \n",
    "        self.bu.setdefault(uid,0)  \n",
    "        \n",
    "        self.qi.setdefault(i_id,np.zeros((self.k,1)))  \n",
    "        self.pu.setdefault(uid,np.zeros((self.k,1)))  \n",
    "        \n",
    "        if (self.qi[i_id].all()==None):  \n",
    "            self.qi[i_id]=np.zeros((self.k,1))  \n",
    "        if (self.pu[uid].all()==None):  \n",
    "            self.pu[uid]=np.zeros((self.k,1))  \n",
    "        \n",
    "        ans=self.mu + self.bi[i_id] + self.bu[uid] + np.sum(self.qi[i_id]*self.pu[uid])  \n",
    "        \n",
    "        #将打分范围控制在1-5之间\n",
    "        if ans>5:  \n",
    "            return 5  \n",
    "        elif ans<1:  \n",
    "            return 1  \n",
    "        return ans  \n",
    "    \n",
    "    #gamma：为学习率\n",
    "    #Lambda：正则参数\n",
    "    def train(self,steps=50,gamma=0.04,Lambda=0.15):  \n",
    "        for step in range(steps):  \n",
    "            print ('the ',step,'-th  step is running'  )\n",
    "            rmse_sum=0.0 \n",
    "            \n",
    "            #将训练样本打散顺序\n",
    "            kk = np.random.permutation(self.X.shape[0])  \n",
    "            for j in range(self.X.shape[0]):  \n",
    "                \n",
    "                #每次一个训练样本\n",
    "                i=kk[j]  \n",
    "                uid=self.X[i][0]  \n",
    "                i_id=self.X[i][1]  \n",
    "                rat=self.X[i][2]  \n",
    "                \n",
    "                #预测残差\n",
    "                eui=rat-self.pred(uid,i_id)  \n",
    "                #残差平方和\n",
    "                rmse_sum+=eui**2  \n",
    "                \n",
    "                #随机梯度下降，更新\n",
    "                self.bu[uid]+=gamma*(eui-Lambda*self.bu[uid])  \n",
    "                self.bi[i_id]+=gamma*(eui-Lambda*self.bi[i_id]) \n",
    "                \n",
    "                temp=self.qi[i_id]  \n",
    "                self.qi[i_id]+=gamma*(eui*self.pu[uid]-Lambda*self.qi[i_id])  \n",
    "                self.pu[uid]+=gamma*(eui*temp-Lambda*self.pu[uid])  \n",
    "            \n",
    "            #学习率递减\n",
    "            gamma=gamma*0.93  \n",
    "            print (\"the rmse of this step on train data is \",np.sqrt(rmse_sum/self.X.shape[0]))  \n",
    "            #self.test(test_data)  \n",
    "            \n",
    "    def test(self,test_X):  \n",
    "        output=[]  \n",
    "        sums=0  \n",
    "        test_X=np.array(test_X)  \n",
    "          \n",
    "        for i in range(test_X.shape[0]):  #对每个测试样本\n",
    "            pre=self.pred(test_X[i][0],test_X[i][1])  #预测打分\n",
    "            output.append(pre)  \n",
    "            sums+=(pre-test_X[i][2])**2     #残差平方和\n",
    "        rmse=np.sqrt(sums/test_X.shape[0])  #均方误差\n",
    "        print (\"the rmse on test data is \",rmse ) \n",
    "        return output  "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
